#ifndef _IMPLEMENTATION_HPP
#define _IMPLEMENTATION_HPP

#include "interface.hpp"
#include "data.hpp"

#include <CL/cl.hpp>

#include <string>
#include <iostream>


static inline void checkErr(cl_int err, const char * name){
    if (err != CL_SUCCESS) {
            std::cerr << "ERROR: " << name << " (" << err << ")" << std::endl;
            exit(EXIT_FAILURE);
    }
}



/*
 *  * Final implementation of the tested OCL program.
 *   */
class OCLProgram_Implementation : public OCLProgram
{
private:
    cl::Buffer * mBuffers;
    cl::CommandQueue * mCommandQueue;
    std::map<std::string, cl::Kernel> * mCompiledKernels;


    cl::Kernel & getKernel(const std::string & str){
        return (*mCompiledKernels)[str];
    }

    enum {
        IN = 0,
        TMP = 1,
        OUT = 2
    };

    cl::Buffer & getBuffer( int type ){
        return (mBuffers[type]);
    }
    cl::CommandQueue & getCommandQueue(){
        return *mCommandQueue;
    }

    inline void dbgMsg( bool verbose,Stopwatch * stopwatch, const char * str){
        if (verbose){
            stopwatch->stop();
            std::cout << str << stopwatch->getMiliseconds() << " ms" << std::endl;
            stopwatch->start();
        }
    }

    void prepareInput(){
        size_t size = (mDataIn.set1.size() + mDataIn.set2.size()) * sizeof(unsigned);

        cl_int err = getCommandQueue().enqueueWriteBuffer(getBuffer(IN),CL_TRUE, 0, size/2, (const void *) &mDataIn.set1.front() );
        checkErr(err, "CommandQueue::enequeueWriteBuffer");

        err = getCommandQueue().enqueueWriteBuffer(getBuffer(IN),CL_TRUE, size/2, size/2, (const void *) &mDataIn.set2.front() );
        checkErr(err, "CommandQueue::enequeueWriteBuffer");

        /*wait till writes are finished
 *         commandQueue.finish();*/
    }


    void computeMinMax( cl_uint * pmin, cl_uint * pmax){

        cl_int err;
        cl_uint num_src_items = (mDataIn.set1.size() + mDataIn.set2.size());

        cl_uint compute_units;
        size_t global_work_size;
        size_t local_work_size;
        size_t num_groups;
/*        clGetDeviceInfo( device, CL_DEVICE_MAX_COMPUTE_UNITS, sizeof(cl_uint), &compute_units, NULL);*/
                                                                                                                               
        compute_units = 512;
        cl_uint ws = 64;
        /*global_work_size = compute_units * 7 * ws; 7 wavefronts per SIMD*/
        global_work_size = compute_units * ws;
        while( (num_src_items / 4) % global_work_size != 0 )
            global_work_size += ws;
        local_work_size = ws;

        num_groups = global_work_size / local_work_size;

        cl::Kernel & minMaxP = getKernel("minMaxP");
        err = minMaxP.setArg( 0, getBuffer(IN) );
        checkErr(err,"minMaxP::setArgument:0");
        err = minMaxP.setArg( 1, getBuffer(OUT) );
        checkErr(err,"minMaxP::setArgument:1");
        err = minMaxP.setArg( 2, 1 * sizeof(cl_uint), (void *) NULL );    //local args
        checkErr(err,"minMaxP::setArgument:2");
        err = minMaxP.setArg( 3, 1 * sizeof(cl_uint), (void *) NULL );    //local args
        checkErr(err,"minMaxP::setArgument:3");
        err = minMaxP.setArg( 4, sizeof(num_src_items), (void*) &num_src_items );
        checkErr(err,"minMaxP::setArgument:4");
	err = minMaxP.setArg(5,getBuffer(TMP));
	checkErr(err,"minMaxP::setArgument:5");

        cl::Kernel & minMaxReduceP = getKernel("minMaxReduceP");
        err = minMaxReduceP.setArg( 0, getBuffer(OUT) );
        checkErr(err,"minMaxReduceP::setArgument:0");

        std::vector<cl::Event> events;
        events.push_back(cl::Event());
        cl::Event & ev = events.front();




        err = getCommandQueue().enqueueNDRangeKernel(
              minMaxP,cl::NullRange,cl::NDRange(global_work_size),cl::NDRange(local_work_size),0,&ev
        );
        checkErr(err,"commandQueue::enequeueNDRange(minMaxP)");
        err = getCommandQueue().enqueueNDRangeKernel(
              minMaxReduceP,cl::NullRange,cl::NDRange(num_groups),cl::NDRange(1),&events,NULL
        );
        checkErr(err,"commandQueue::enequeueNDRange(minMaxReduceP)");

        getCommandQueue().finish();
        cl_uint out[2];
        err = getCommandQueue().enqueueReadBuffer(getBuffer(OUT),CL_TRUE,0,sizeof(cl_uint) * 2, (void*)out);
	checkErr(err,"commandQueue::enequeueReadBuffer(Out)");
        *pmin = out[0];
        *pmax = out[1];

	cl_uint dbg_ptr[4];
	err = getCommandQueue().enqueueReadBuffer(getBuffer(TMP),CL_TRUE,0,sizeof(cl_uint) * 4, (void *)dbg_ptr);

	 printf("%d groups, %d threads, count %d, stride %d\n", dbg_ptr[0],dbg_ptr[1], dbg_ptr[2],dbg_ptr[3] );

    }

    void checkMinMax(cl_uint min, cl_uint max){
        unsigned m1 = *std::min_element(mDataIn.set1.begin(),mDataIn.set1.end());
        unsigned m2 = *std::min_element(mDataIn.set2.begin(),mDataIn.set2.end());
        unsigned m = std::min(m1,m2);
        unsigned M1 = *std::max_element(mDataIn.set1.begin(),mDataIn.set1.end());
        unsigned M2 = *std::max_element(mDataIn.set2.begin(),mDataIn.set2.end());
        unsigned M = std::max(M1,M2);
        std::cout << "min element: " << (min == m ? "ok" : "fail") << std::endl;
        std::cout << "max element: " << (max == M ? "ok" : "fail") << std::endl;
    }

    void computeBuckets( cl_uint L, cl_uint min, cl_uint max){

        cl_int err;

        cl::Kernel & histogram = getKernel("histogram");

        err = histogram.setArg( 0, getBuffer(IN) );
        checkErr(err,"histogram::setArgument:0");
        err = histogram.setArg( 1, getBuffer(TMP) );
        checkErr(err,"histogram::setArgument:1");
        err = histogram.setArg( 2, sizeof(cl_uint), (void*) &min );
        checkErr(err,"histogram::setArgument:2");
        err = histogram.setArg( 3, sizeof(cl_uint), (void*) &max );
        checkErr(err,"histogram::setArgument:3");
        err = histogram.setArg( 4, sizeof(cl_uint), (void*) &L );
        checkErr(err,"histogram::setArgument:4");
	err = histogram.setArg(5, sizeof(cl_uint) * L, (void*) NULL);
	checkErr(err,"histogram::setArgument:5");

        cl_uint num_src_items = (mDataIn.set1.size() + mDataIn.set2.size());

        err = getCommandQueue().enqueueNDRangeKernel(histogram,cl::NullRange,cl::NDRange(num_src_items),cl::NullRange);
        checkErr(err,"commandQueue::enequeue(histogram)");
        err = getCommandQueue().finish();
        checkErr(err,"commandQueue::enequeue(histogram) - finish");

    }


public:
	OCLProgram_Implementation(const DataIn &dataIn, DataOut &dataOut) : OCLProgram(dataIn, dataOut)
        {
                /* This is the best place to initialize mKernels member field.
 *                  i.e. mKernels["kernelName"] = Source("kernelFile.cl", true);
 *                                    mKernels["sortElements"] = Source("sortElements.cl",true);*/
                  mKernels["minMaxP"] = Source("minMaxP.cl",true);
                  mKernels["minMaxReduceP"] = Source("minMaxReduceP.cl",true);
		  mKernels["histogram"] = Source("histogram.cl",true);
	}

	virtual int run(const cl::Context &context, const std::vector<cl::Device> &devices,
		std::map<std::string, cl::Kernel> &kernels, bool verbose)
	{
		/*
 * 		 * Implementation of the main algorithm.
 * 		 		 * Note that you have mDataIn and mDataOut member fields at your disposal.
 *
 * 		 		                  */
            cl_int err;

            mCompiledKernels = &kernels;

            size_t size = (mDataIn.set1.size() + mDataIn.set2.size()) * sizeof(unsigned);


            cl::Buffer bufs []= {
                cl::Buffer(context, CL_MEM_READ_WRITE, size, NULL, &err ),  /*in*/
                cl::Buffer(context, CL_MEM_READ_WRITE, size, NULL, &err ),  /*tmp*/
                cl::Buffer(context, CL_MEM_READ_WRITE, size, NULL, &err )   /*out*/
            };
            checkErr(err, "cl::Buffer");
            mBuffers = bufs;

            cl::CommandQueue commandQueue(context, devices[0], 0, &err);
            checkErr(err, "CommandQueue");
            mCommandQueue = &commandQueue;


            Stopwatch * stopwatch = NULL;
            if (verbose){
                stopwatch = new Stopwatch(true);
            }

            prepareInput();
            dbgMsg(verbose, stopwatch,"Creating buffers: ");

            cl_uint min, max;
            computeMinMax( &min, & max);
            dbgMsg(verbose, stopwatch,"Computing min and max: ");
            if (verbose){
                checkMinMax(min,max);
                stopwatch->stop();stopwatch->start();
            }

	    cl_uint L = 512 * 32;
	    computeBuckets(L, min, max);
	    dbgMsg(verbose, stopwatch,"Computing buckets:  ");	




            if (verbose){
                stopwatch->stop();
                delete stopwatch;
            }

            return 0;	// Success
	}
};





















#endif


